/*******************************************************************************
 * Copyright (c) 2007, 2016 Wind River and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     Wind River - Initial API and implementation
 *     Ericsson   - High-level breakpoints integration
 *     Ericsson   - Added breakpoint filter support
 *     Ericsson   - Re-factored the service and put a few comments
 *     Ericsson   - Added Action support
 *     Marc Khouzam (Ericsson) - Fix support for thread filter (Bug 355833)
 *     Marc Khouzam (Ericsson) - Generalize thread filtering logic (Bug 431986)
 *     Marc Khouzam (Ericsson) - Accept multiple calls to startTrackingBreakpoints (Bug 389945)
 *     Marc Khouzam (Ericsson) - Support for dynamic printf (Bug 400628)
 *     Alvaro Sanchez-Leon (Ericsson) - Sometimes breakpoints set and immediately deleted when debugging with GDB (Bug 442394)
 *     Alvaro Sanchez-Leon (Ericsson) - Breakpoint Enable does not work after restarting the application (Bug 456959)
 *******************************************************************************/

package org.eclipse.cdt.dsf.mi.service;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Vector;
import java.util.concurrent.RejectedExecutionException;

import org.eclipse.cdt.core.model.ICModelMarker;
import org.eclipse.cdt.debug.core.CDebugCorePlugin;
import org.eclipse.cdt.debug.core.breakpointactions.BreakpointActionManager;
import org.eclipse.cdt.debug.core.model.ICAddressBreakpoint;
import org.eclipse.cdt.debug.core.model.ICBreakpoint;
import org.eclipse.cdt.debug.core.model.ICBreakpointExtension;
import org.eclipse.cdt.debug.core.model.ICBreakpointType;
import org.eclipse.cdt.debug.core.model.ICDynamicPrintf;
import org.eclipse.cdt.debug.core.model.ICEventBreakpoint;
import org.eclipse.cdt.debug.core.model.ICFunctionBreakpoint;
import org.eclipse.cdt.debug.core.model.ICLineBreakpoint;
import org.eclipse.cdt.debug.core.model.ICTracepoint;
import org.eclipse.cdt.debug.core.model.ICWatchpoint;
import org.eclipse.cdt.debug.core.model.ICWatchpoint2;
import org.eclipse.cdt.debug.internal.core.breakpoints.BreakpointProblems;
import org.eclipse.cdt.dsf.concurrent.CountingRequestMonitor;
import org.eclipse.cdt.dsf.concurrent.DataRequestMonitor;
import org.eclipse.cdt.dsf.concurrent.DsfRunnable;
import org.eclipse.cdt.dsf.concurrent.ImmediateDataRequestMonitor;
import org.eclipse.cdt.dsf.concurrent.ImmediateExecutor;
import org.eclipse.cdt.dsf.concurrent.ImmediateRequestMonitor;
import org.eclipse.cdt.dsf.concurrent.RequestMonitor;
import org.eclipse.cdt.dsf.concurrent.ThreadSafe;
import org.eclipse.cdt.dsf.datamodel.DMContexts;
import org.eclipse.cdt.dsf.datamodel.IDMContext;
import org.eclipse.cdt.dsf.debug.service.IBreakpoints;
import org.eclipse.cdt.dsf.debug.service.IBreakpoints.IBreakpointDMContext;
import org.eclipse.cdt.dsf.debug.service.IBreakpoints.IBreakpointDMData;
import org.eclipse.cdt.dsf.debug.service.IBreakpoints.IBreakpointsTargetDMContext;
import org.eclipse.cdt.dsf.debug.service.IDsfBreakpointExtension;
import org.eclipse.cdt.dsf.debug.service.IProcesses;
import org.eclipse.cdt.dsf.debug.service.IRunControl.IContainerDMContext;
import org.eclipse.cdt.dsf.debug.service.IRunControl.IExecutionDMContext;
import org.eclipse.cdt.dsf.debug.service.IRunControl.IExitedDMEvent;
import org.eclipse.cdt.dsf.debug.service.IRunControl.IStartedDMEvent;
import org.eclipse.cdt.dsf.debug.service.IRunControl.ISuspendedDMEvent;
import org.eclipse.cdt.dsf.debug.service.ISourceLookup;
import org.eclipse.cdt.dsf.debug.service.ISourceLookup.ISourceLookupDMContext;
import org.eclipse.cdt.dsf.debug.service.command.ICommandControlService;
import org.eclipse.cdt.dsf.debug.service.command.ICommandControlService.ICommandControlShutdownDMEvent;
import org.eclipse.cdt.dsf.gdb.internal.GdbPlugin;
import org.eclipse.cdt.dsf.mi.service.MIBreakpoints.BreakpointAddedEvent;
import org.eclipse.cdt.dsf.mi.service.MIBreakpoints.BreakpointRemovedEvent;
import org.eclipse.cdt.dsf.mi.service.MIBreakpoints.BreakpointUpdatedEvent;
import org.eclipse.cdt.dsf.mi.service.MIBreakpoints.MIBreakpointDMContext;
import org.eclipse.cdt.dsf.mi.service.MIRunControl.SuspendedEvent;
import org.eclipse.cdt.dsf.mi.service.breakpoint.actions.BreakpointActionAdapter;
import org.eclipse.cdt.dsf.mi.service.command.events.IMIDMEvent;
import org.eclipse.cdt.dsf.mi.service.command.events.MIBreakpointHitEvent;
import org.eclipse.cdt.dsf.mi.service.command.events.MIWatchpointScopeEvent;
import org.eclipse.cdt.dsf.mi.service.command.events.MIWatchpointTriggerEvent;
import org.eclipse.cdt.dsf.mi.service.command.output.MIBreakpoint;
import org.eclipse.cdt.dsf.service.AbstractDsfService;
import org.eclipse.cdt.dsf.service.DsfServiceEventHandler;
import org.eclipse.cdt.dsf.service.DsfSession;
import org.eclipse.cdt.gdb.internal.eventbkpts.GdbCatchpoints;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IMarkerDelta;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IWorkspaceRunnable;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.ListenerList;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.ISchedulingRule;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.core.runtime.jobs.MultiRule;
import org.eclipse.debug.core.DebugPlugin;
import org.eclipse.debug.core.IBreakpointListener;
import org.eclipse.debug.core.IBreakpointManager;
import org.eclipse.debug.core.IBreakpointManagerListener;
import org.eclipse.debug.core.model.IBreakpoint;
import org.osgi.framework.BundleContext;

import com.ibm.icu.text.MessageFormat;

/**
 * Breakpoint service interface.  The breakpoint service tracks CDT breakpoint
 * objects, and based on those, it manages breakpoints in the debugger back end.
 * 
 * It relies on MIBreakpoints for the actual back-end interface.
 */
public class MIBreakpointsManager extends AbstractDsfService implements IBreakpointManagerListener, IBreakpointListener
{
    /**
     * A listener is notified by {@link MIBreakpointsManager} when 
     * the breakpoints tracking starts or stops. 
     * @since 4.2
     */
    public interface IMIBreakpointsTrackingListener {

    	public void breakpointTrackingStarted(IBreakpointsTargetDMContext bpTargetDMC);
    	
    	public void breakpointTrackingStopped(IBreakpointsTargetDMContext bpTargetDMC);
	}

	// Note: Find a way to import this (careful of circular dependencies)
    public static final String GDB_DEBUG_MODEL_ID = "org.eclipse.cdt.dsf.gdb"; //$NON-NLS-1$

    // Extra breakpoint attributes
    private static final String ATTR_DEBUGGER_PATH = GdbPlugin.PLUGIN_ID + ".debuggerPath";   //$NON-NLS-1$
    private static final String ATTR_THREAD_FILTER = GdbPlugin.PLUGIN_ID + ".threadFilter";   //$NON-NLS-1$
    private static final String ATTR_THREAD_ID     = GdbPlugin.PLUGIN_ID + ".threadID";       //$NON-NLS-1$

    // Services
    private ICommandControlService fConnection;
    private ISourceLookup      fSourceLookup;
    private IProcesses         fProcesses;
    private IBreakpoints       fBreakpoints;
    private IBreakpointManager fBreakpointManager;  // Platform breakpoint manager (not this!)
    private BreakpointActionManager fBreakpointActionManager;
    
    ///////////////////////////////////////////////////////////////////////////
    // Breakpoints tracking
    ///////////////////////////////////////////////////////////////////////////

    private String fDebugModelId;
    
    // Holds the set of platform breakpoints with their corresponding back-end
    // breakpoint attributes, per context (i.e. each platform breakpoint is
    // replicated for each execution context).
    // - Context entry added/removed on start/stopTrackingBreakpoints()
    // - Augmented on breakpointAdded()
    // - Modified on breakpointChanged()
    // - Diminished on breakpointRemoved()
    private Map<IBreakpointsTargetDMContext, Map<ICBreakpoint, Map<String, Object>>> fPlatformToAttributesMaps = new HashMap<>();

	/**
	 * Returns the structure that maps each breakpoint target to a map of platform breakpoints 
	 * and their corresponding back-end attributes.
	 * @since 4.7
	 */
	protected Map<IBreakpointsTargetDMContext, Map<ICBreakpoint, Map<String, Object>>> getPlatformToAttributesMaps() {
		return fPlatformToAttributesMaps;
	}

    // Holds the set of target breakpoints, per execution context, and their
    // mapping to the corresponding platform breakpoint. In a given execution
    // context there can only be one platform breakpoint per target breakpoint.
    // Acts as a mapping from target (low-level) BP to the corresponding platform
    // (high-level) BP.
    // Updated when:
    // - We start/stop tracking an execution context
    // - A platform breakpoint is added/removed
    // - A thread filter is applied/removed
    private Map<IBreakpointsTargetDMContext, Map<IBreakpointDMContext, ICBreakpoint>> fBPToPlatformMaps = new HashMap<>();

    /**
	 * Returns the structure that maps each breakpoint target to a map of back-end breakpoints 
	 * and their corresponding platform breakpoint.
	 * @since 4.7
	 */
    protected Map<IBreakpointsTargetDMContext, Map<IBreakpointDMContext, ICBreakpoint>> getBPToPlatformMaps() {
    	return fBPToPlatformMaps;
    }
    
    // Holds the mapping from platform breakpoint to the corresponding target
    // breakpoint(s), per context. There can be multiple back-end BPs for a
    // single platform BP in the case of [1] multiple target contexts, and/or
    // [2] thread filtering.
    // Updated when:
    // - We start/stop tracking an execution context
    // - A platform breakpoint is added/removed
    // - A thread filter is applied/removed
    private Map<IBreakpointsTargetDMContext, Map<ICBreakpoint, Vector<IBreakpointDMContext>>> fPlatformToBPsMaps = new HashMap<>();

    /**
	 * Returns the structure that maps each breakpoint target to a map of platform breakpoints 
	 * and their corresponding vector of back-end breakpoints.
	 * @since 4.7
	 */
    protected Map<IBreakpointsTargetDMContext, Map<ICBreakpoint, Vector<IBreakpointDMContext>>> getPlatformToBPsMaps() {
    	return fPlatformToBPsMaps;
    }

    // Holds the mapping from platform breakpoint to the corresponding target
    // breakpoint threads, per context.
    // Updated when:
    // - We start/stop tracking an execution context
    // - A platform breakpoint is added/removed
    // - A thread filter is applied/removed
    private Map<IBreakpointsTargetDMContext, Map<ICBreakpoint, Set<String>>> fPlatformToBPThreadsMaps = new HashMap<>();

    /**
	 * Returns the structure that maps each breakpoint target to a map of platform breakpoints 
	 * and their corresponding back-end breakpoint thread ids.
	 * @since 4.7
	 */
    protected Map<IBreakpointsTargetDMContext, Map<ICBreakpoint, Set<String>>> getPlatformToBPThreadsMaps() {
    	return fPlatformToBPThreadsMaps;
    }

    // Due to the very asynchronous nature of DSF, a new breakpoint request can
    // pop up at any time before an ongoing one is completed. The following set
    // is used to store requests until the ongoing operation completes.
    private Set<IBreakpoint> fPendingRequests    = new HashSet<>();
    private Set<IBreakpoint> fPendingBreakpoints = new HashSet<>();

    private Map<ICBreakpoint, IMarker> fBreakpointMarkerProblems = new HashMap<>();

    private ListenerList fTrackingListeners = new ListenerList();

    ///////////////////////////////////////////////////////////////////////////
    // String constants
    ///////////////////////////////////////////////////////////////////////////

    private static final String NULL_STRING = ""; //$NON-NLS-1$

    static final String CONTEXT_ALREADY_INITIALIZED  = "Context already initialized";  //$NON-NLS-1$
    static final String INVALID_CONTEXT_TYPE         = "Invalid context type";         //$NON-NLS-1$
    static final String INVALID_CONTEXT              = "Invalid context";              //$NON-NLS-1$

    static final String UNABLE_TO_READ_BREAKPOINT    = "Unable to read initial breakpoint attributes"; //$NON-NLS-1$
    static final String BREAKPOINT_NOT_INSTALLED     = "Breakpoints not installed for given context";  //$NON-NLS-1$
    static final String BREAKPOINT_ALREADY_INSTALLED = "Breakpoint already installed"; //$NON-NLS-1$
    static final String BREAKPOINT_ALREADY_REMOVED   = "Breakpoint already removed";   //$NON-NLS-1$

    static final String INVALID_BREAKPOINT           = "Invalid breakpoint";                    //$NON-NLS-1$
    static final String UNKNOWN_BREAKPOINT           = "Unknown breakpoint";                    //$NON-NLS-1$
    static final String INVALID_PARAMETER            = "Invalid breakpoint parameter(s)";       //$NON-NLS-1$

    static final String NO_DEBUGGER_PATH             = "No debugger path for breakpoint";       //$NON-NLS-1$
    static final String NO_MARKER_FOR_BREAKPOINT     = "No marker associated with breakpoint";  //$NON-NLS-1$

    ///////////////////////////////////////////////////////////////////////////
    // AbstractDsfService
    ///////////////////////////////////////////////////////////////////////////

    /**
     * The service constructor.
     * Performs basic instantiation (method initialize() performs the real
     * service initialization asynchronously).
     * 
     * @param session       the debugging session
     * @param debugModelId  the debugging model
     */
    public MIBreakpointsManager(DsfSession session, String debugModelId) {
        super(session);
        fDebugModelId = debugModelId;
    }

    //-------------------------------------------------------------------------
    // initialize
    //-------------------------------------------------------------------------
    // - Collect references for the services we interact with
    // - Register to interesting events
    // - Obtain the list of platform breakpoints
    // - Register the service for interested parties
    //-------------------------------------------------------------------------

    /* (non-Javadoc)
     * @see org.eclipse.cdt.dsf.service.AbstractDsfService#initialize(org.eclipse.cdt.dsf.concurrent.RequestMonitor)
     */
    @Override
    public void initialize(final RequestMonitor rm) {
        super.initialize(
            new ImmediateRequestMonitor(rm) {
                @Override
                protected void handleSuccess() {
                    doInitialize(rm);
                }});
    }

    /**
     * @param rm
     */
    private void doInitialize(RequestMonitor rm) {
        
        // Get the required services references from central repository
        fConnection     = getServicesTracker().getService(ICommandControlService.class);
        fSourceLookup   = getServicesTracker().getService(ISourceLookup.class);
        fBreakpoints    = getServicesTracker().getService(IBreakpoints.class);
        fProcesses      = getServicesTracker().getService(IProcesses.class);
        fBreakpointManager = DebugPlugin.getDefault().getBreakpointManager();
        fBreakpointActionManager = CDebugCorePlugin.getDefault().getBreakpointActionManager();

        // Register to the useful events
        getSession().addServiceEventListener(this, null);
        fBreakpointManager.addBreakpointListener(this);
        fBreakpointManager.addBreakpointManagerListener(this);

        // And register this service
        register(new String[] { MIBreakpointsManager.class.getName() },
                 new Hashtable<String, String>());
        rm.done();
    }

    //-------------------------------------------------------------------------
    // shutdown
    //-------------------------------------------------------------------------
    // - Un-register the service
    // - Stop listening to events
    // - Remove the breakpoints installed by this service
    //
    //  Since we are shutting down, there is no overwhelming need
    //  to keep the maps coherent...
    //-------------------------------------------------------------------------
    
    /* (non-Javadoc)
     * @see org.eclipse.cdt.dsf.service.AbstractDsfService#shutdown(org.eclipse.cdt.dsf.concurrent.RequestMonitor)
     */
    @Override
    public void shutdown(final RequestMonitor rm) {

        // Stop accepting requests and events
        unregister();
        getSession().removeServiceEventListener(this);
        fBreakpointManager.removeBreakpointListener(this);
        fBreakpointManager.removeBreakpointManagerListener(this);
        fTrackingListeners.clear();

        // Cleanup the breakpoints that are still installed by the service.
        // Use a counting monitor which will call mom to complete the shutdown
        // after the breakpoints are un-installed (successfully or not).
        CountingRequestMonitor countingRm = new CountingRequestMonitor(getExecutor(), rm) {
            @Override
            protected void handleCompleted() {
                MIBreakpointsManager.super.shutdown(rm);
            }
        };

        List<IBreakpointsTargetDMContext> targetBPKeys = new ArrayList<>(fBPToPlatformMaps.size());
        targetBPKeys.addAll(0, fBPToPlatformMaps.keySet());
        for (IBreakpointsTargetDMContext dmc : targetBPKeys) {
            stopTrackingBreakpoints(dmc, countingRm);
        }
        countingRm.setDoneCount(targetBPKeys.size());
    }

    //-------------------------------------------------------------------------
    // getBundleContext
    //-------------------------------------------------------------------------

    /* (non-Javadoc)
     * @see org.eclipse.cdt.dsf.service.AbstractDsfService#getBundleContext()
     */
    @Override
    protected BundleContext getBundleContext() {
        return GdbPlugin.getBundleContext();
    }

    ///////////////////////////////////////////////////////////////////////////
    // IBreakpointsManager
    ///////////////////////////////////////////////////////////////////////////

    /**
     * Wrapper around startTrackingBreakpoints() which accepts a containerDmc and sets
     * each breakpoints filter to include that container.  This method should be called
     * instead of startTrackingBreakpoints()
     * 
     * @param containerDmc The container to be added in the bp filter.  This container
     *                     must have the proper IBreakpointsTargetDMContext in its hierarchy.
     * 
	 * @since 4.6
	 */    
	public void startTrackingBpForProcess(final IContainerDMContext containerDmc, final RequestMonitor rm) {
		final IBreakpointsTargetDMContext targetBpDmc = DMContexts.getAncestorOfType(containerDmc,
				IBreakpointsTargetDMContext.class);

		IBreakpoint[] breakpoints = fBreakpointManager.getBreakpoints(fDebugModelId);
		for (IBreakpoint breakpoint : breakpoints) {
			if (breakpoint instanceof ICBreakpoint && supportsBreakpoint(breakpoint)) {
				setTargetFilter((ICBreakpoint) breakpoint, containerDmc);
			}
		}
		startTrackingBreakpoints(targetBpDmc, rm);
	}
    
    //-------------------------------------------------------------------------
    // startTrackingBreakpoints
    //-------------------------------------------------------------------------
    // - Augment the maps with the new execution context
    // - Install the platform breakpoints on the selected target
    //-------------------------------------------------------------------------

    /**
     * Install and begin tracking breakpoints for given context.  The service
     * will keep installing new breakpoints that appear in the IDE for this
     * context until {@link #uninstallBreakpoints(IDMContext)} is called for that
     * context.
     * @param dmc Context to start tracking breakpoints for.
     * @param rm Completion callback.
     */
    public void startTrackingBreakpoints(final IBreakpointsTargetDMContext dmc, final RequestMonitor rm) {

        // Validate the execution context
        if (dmc == null) {
            rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, REQUEST_FAILED, INVALID_CONTEXT, null));
            rm.done();
            return;
        }

        Map<ICBreakpoint,Map<String, Object>> platformBPs = fPlatformToAttributesMaps.get(dmc);
        Map<ICBreakpoint, Vector<IBreakpointDMContext>> breakpointIDs = fPlatformToBPsMaps.get(dmc);
        Map<IBreakpointDMContext, ICBreakpoint> targetIDs = fBPToPlatformMaps.get(dmc);
        Map<ICBreakpoint, Set<String>> threadIDs = fPlatformToBPThreadsMaps.get(dmc);
        if ((platformBPs != null) || (breakpointIDs != null) || (targetIDs != null) || (threadIDs != null)) {
        	// If the maps already contains this context we can simply ignore this request.
        	// This happens when we start or attach to another process with GDB >= 7.4
            assert platformBPs != null && breakpointIDs != null && targetIDs != null && threadIDs != null;
            rm.done();
            return;
        }

        // Create entries in the breakpoint tables for the new context. These entries should only
        // be removed when this service stops tracking breakpoints for the given context.
        fPlatformToAttributesMaps.put(dmc, new HashMap<ICBreakpoint, Map<String, Object>>());
        fPlatformToBPsMaps.put(dmc, new HashMap<ICBreakpoint, Vector<IBreakpointDMContext>>());
        fBPToPlatformMaps.put(dmc, new HashMap<IBreakpointDMContext, ICBreakpoint>());
        fPlatformToBPThreadsMaps.put(dmc, new HashMap<ICBreakpoint, Set<String>>());

        // Install the platform breakpoints (stored in fPlatformBPs) on the target.
        new Job("DSF BreakpointsManager: Install initial breakpoints on target") { //$NON-NLS-1$
            @Override
            protected IStatus run(IProgressMonitor monitor) {
                // Submit the runnable to plant the breakpoints on dispatch thread.
                getExecutor().submit(new Runnable() {
                	@Override
                	public void run() {
                        installInitialBreakpoints(dmc, new RequestMonitor(ImmediateExecutor.getInstance(), rm) {
                        	@Override
                        	protected void handleSuccess() {
                        		// Notify breakpoints tracking listeners that the tracking is started.
                        		for (Object o : fTrackingListeners.getListeners()) {
                        			((IMIBreakpointsTrackingListener)o).breakpointTrackingStarted(dmc);
                        		}
                    			rm.done();
                        	};
                        });
                    }
                });

                return Status.OK_STATUS;
            }
        }.schedule();
    }

    /**
     * Installs the breakpoints that existed prior to the activation of this
     * execution context.
     * 
     * @param dmc
     * @param initialPlatformBPs
     * @param rm
     */
    private void installInitialBreakpoints(final IBreakpointsTargetDMContext dmc, final RequestMonitor rm)
    {
        // Retrieve the set of platform breakpoints for this context
        final Map<ICBreakpoint,Map<String, Object>> platformBPs = fPlatformToAttributesMaps.get(dmc);
        if (platformBPs == null) {
            rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INTERNAL_ERROR, INVALID_CONTEXT, null));
            rm.done();
            return;
        }

        // Read current breakpoints from platform and copy their augmented
        // attributes into the local reference map
        try {
            IBreakpoint[] breakpoints = fBreakpointManager.getBreakpoints(fDebugModelId);
            for (IBreakpoint breakpoint : breakpoints) {
                if (supportsBreakpoint(breakpoint)) {
                    boolean filtered = isBreakpointEntirelyFiltered(dmc, (ICBreakpoint) breakpoint);
                    if (!filtered) {
                        Map<String, Object> attributes = breakpoint.getMarker().getAttributes();
                        attributes.put(ATTR_DEBUGGER_PATH, NULL_STRING);
                        attributes.put(ATTR_THREAD_FILTER, extractThreads(dmc, (ICBreakpoint) breakpoint));
                        attributes.put(ATTR_THREAD_ID, NULL_STRING);
                        platformBPs.put((ICBreakpoint) breakpoint, attributes);
                    }
                }
            }
        } catch (CoreException e) {
            IStatus status = new Status(
                IStatus.ERROR, GdbPlugin.PLUGIN_ID, REQUEST_FAILED, UNABLE_TO_READ_BREAKPOINT, e);
            rm.setStatus(status);
            rm.done();
        }

        // Install the individual breakpoints on the dispatcher thread
        // Requires a counting monitor to know when we are done
        final CountingRequestMonitor countingRm = new CountingRequestMonitor(getExecutor(), rm);
        countingRm.setDoneCount(platformBPs.size());

        for (final ICBreakpoint breakpoint : platformBPs.keySet()) {
            final Map<String, Object> attributes = platformBPs.get(breakpoint);
            // Upon determining the debuggerPath, the breakpoint is installed
            determineDebuggerPath(dmc, attributes, new RequestMonitor(getExecutor(), countingRm) {
                @Override
                protected void handleSuccess() {                	
                	// Must install breakpoints right away, even if disabled, so that
                	// we can find out if they apply to this target (Bug 389070)
               		installBreakpoint(dmc, breakpoint, attributes, countingRm);
                }
            });
        }
    }

    //-------------------------------------------------------------------------
    // stopTrackingBreakpoints
    //-------------------------------------------------------------------------
    // - Remove the target breakpoints for the given execution context
    // - Update the maps
    //-------------------------------------------------------------------------

    /**
     * Uninstall and stop tracking breakpoints for the given context.
     * @param dmc Context to start tracking breakpoints for.
     * @param rm Completion callback.
     */
    public void stopTrackingBreakpoints(final IBreakpointsTargetDMContext dmc, final RequestMonitor rm) {

        // Validate the context
        if (dmc == null) {
            rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INTERNAL_ERROR, INVALID_CONTEXT, null));
            rm.done();
            return;
        }

        // Retrieve the set of platform breakpoints for this context
        final Map<ICBreakpoint,Map<String, Object>> platformBPs = fPlatformToAttributesMaps.get(dmc);
        if (platformBPs == null) {
            rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INTERNAL_ERROR, INVALID_CONTEXT, null));
            rm.done();
            return;
        }

        // Un-install the individual breakpoints on the dispatcher thread
        // (requires a counting monitor to know when we are done).
        // On completion (success or failure), update the maps.
        final CountingRequestMonitor countingRm = new CountingRequestMonitor(getExecutor(), rm) {
            @Override
            protected void handleCompleted() {
                fPlatformToAttributesMaps.remove(dmc);
                fPlatformToBPsMaps.remove(dmc);
                fBPToPlatformMaps.remove(dmc);
                fPlatformToBPThreadsMaps.remove(dmc);
        		// Notify breakpoints tracking listeners that the tracking is stopped.
        		for (Object o : fTrackingListeners.getListeners()) {
        			((IMIBreakpointsTrackingListener)o).breakpointTrackingStopped(dmc);
        		}
                rm.done();
            }
        };
        countingRm.setDoneCount(platformBPs.size());

        for (final ICBreakpoint breakpoint : platformBPs.keySet()) {
            uninstallBreakpoint(dmc, breakpoint,
                new RequestMonitor(getExecutor(), countingRm) {
                    @Override
                    protected void handleCompleted() {
                        countingRm.done();
                    }
                });
        }
    }

    ///////////////////////////////////////////////////////////////////////////
    // Back-end interface functions
    ///////////////////////////////////////////////////////////////////////////

    //-------------------------------------------------------------------------
    // installBreakpoint
    //-------------------------------------------------------------------------

    /**
     * Install a platform breakpoint on the back-end. For a given context, a
     * platform breakpoint can resolve into multiple back-end breakpoints when
     * threads are taken into account or if multiple breakpoints are created
     * on the target using the console.
     * 
     * @param dmc
     * @param breakpoint
     * @param attributes
     * @param rm
     */
    private void installBreakpoint(IBreakpointsTargetDMContext dmc, final ICBreakpoint breakpoint,
        final Map<String, Object> attributes, final RequestMonitor rm)
    {
        // Retrieve the breakpoint maps
        final Map<ICBreakpoint,Map<String,Object>> platformBPs = fPlatformToAttributesMaps.get(dmc);
        assert platformBPs != null;

        final Map<ICBreakpoint, Vector<IBreakpointDMContext>> breakpointIDs = fPlatformToBPsMaps.get(dmc);
        assert breakpointIDs != null;

        final Map<IBreakpointDMContext, ICBreakpoint> targetBPs = fBPToPlatformMaps.get(dmc);
        assert targetBPs != null;

        final Map<ICBreakpoint, Set<String>> threadsIDs = fPlatformToBPThreadsMaps.get(dmc);
        assert threadsIDs != null;

        // Ensure the breakpoint has a valid debugger source path
        if (breakpoint instanceof ICLineBreakpoint 
        	&& !(breakpoint instanceof ICAddressBreakpoint)
        	&& !(breakpoint instanceof ICFunctionBreakpoint)) {
            String debuggerPath = (String) attributes.get(ATTR_DEBUGGER_PATH);
            if (debuggerPath == null || debuggerPath.equals(NULL_STRING)) {
                rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, REQUEST_FAILED, NO_DEBUGGER_PATH, null));
                rm.done();
                return;
            }
        }

        // A back-end breakpoint needs to be installed for each specified thread
        final Set<String> threads = getThreads(attributes);

        // Update the breakpoint state when all back-end breakpoints have been installed
        final CountingRequestMonitor installRM = new CountingRequestMonitor(getExecutor(), rm) {
            @Override
            protected void handleCompleted() {
                // Store the platform breakpoint
                platformBPs.put(breakpoint, attributes);
                rm.done();
            }
        };
        installRM.setDoneCount(threads.size());

        // Install the back-end breakpoint(s)
        for (final String thread : threads) {
            DataRequestMonitor<IBreakpointDMContext> drm =
                new DataRequestMonitor<IBreakpointDMContext>(getExecutor(), installRM) {
                    @Override
                    protected void handleSuccess() {
                        // Add the new back-end breakpoint to the map
                        Vector<IBreakpointDMContext> list = breakpointIDs.get(breakpoint);
                        if (list == null) {
                        	list = new Vector<>();
                        }
                        
                        IBreakpointDMContext targetBP = getData();
                        list.add(targetBP);
                        breakpointIDs.put(breakpoint, list);

                        // Add the reverse mapping
                        targetBPs.put(targetBP, breakpoint);

                        // And update the corresponding thread list
                        Set<String> thrds = threadsIDs.get(breakpoint);
                        if (thrds == null) {
                        	thrds = new HashSet<>();
                        }
                        
                        thrds.add(thread);
                        threadsIDs.put(breakpoint, thrds);

                        // Reset the thread (is it necessary?)
                        attributes.put(ATTR_THREAD_ID, NULL_STRING);

                        // Remove breakpoint problem marker (if any)
                        removeBreakpointProblemMarker(breakpoint);

                        // Check for a pending breakpoint before showing that it was properly installed
                        fBreakpoints.getBreakpointDMData(targetBP, new DataRequestMonitor<IBreakpointDMData>(getExecutor(), null) {
                        	@Override
                        	protected void handleCompleted() {
                        		boolean pending = false;
                        		if (isSuccess()) {
                        			IBreakpointDMData data = getData();
                        			if (data instanceof MIBreakpointDMData) {
                        				pending = ((MIBreakpointDMData)data).isPending();
                        			}
                        		}
                        		// Finally, update the platform breakpoint to show it was installed, unless we have a pending breakpoint
                        		if (!pending) {
                        			try {
                        				breakpoint.incrementInstallCount();
                        			} catch (CoreException e) {
                        			}
                        		}
                        		installRM.done();                        		
                        	}
                        });
                    }

                    @Override
                    protected void handleError() {
                    	String detailedMessage;
                    	if (getStatus().getException() != null &&
                    			getStatus().getException().getMessage() != null) {
                    		detailedMessage = getStatus().getException().getMessage();
                    	} else {
                    		detailedMessage = getStatus().getMessage();
                    	}
                    	String description = (detailedMessage == null) ? 
                    			Messages.Breakpoint_attribute_problem :
                    			MessageFormat.format(Messages.Breakpoint_attribute_detailed_problem, new Object[] { detailedMessage});

                        addBreakpointProblemMarker(breakpoint, description, IMarker.SEVERITY_WARNING);
                        installRM.done();
                    }
                };

            // Convert the breakpoint attributes for the back-end
            attributes.put(ATTR_THREAD_ID, thread);
            Map<String,Object> targetAttributes = convertToTargetBreakpoint(breakpoint, attributes);
        	// Must install breakpoint right away, even if disabled, so that
        	// we can find out if it applies to this target (Bug 389070)
            fBreakpoints.insertBreakpoint(dmc, targetAttributes, drm);
        }
    }

    private void addBreakpointProblemMarker(final ICBreakpoint breakpoint, final String description, final int severity) {

        new Job("Add Breakpoint Problem Marker") { //$NON-NLS-1$
            @Override
            protected IStatus run(IProgressMonitor monitor) {
                
                if (breakpoint instanceof ICLineBreakpoint) {
                	// If we have already have a problem marker on this breakpoint
                	// we should remove it first.
                    IMarker marker = fBreakpointMarkerProblems.remove(breakpoint);
                    if (marker != null) {
                        try {
                            marker.delete();
                        } catch (CoreException e) {
                        }
                	}

                    ICLineBreakpoint lineBreakpoint = (ICLineBreakpoint) breakpoint;
                    try {
                        // Locate the workspace resource via the breakpoint marker
                        IMarker breakpointMarker = lineBreakpoint.getMarker();
                        IResource resource = breakpointMarker.getResource();

                        // Add a problem marker to the resource
                        IMarker problemMarker = resource.createMarker(BreakpointProblems.BREAKPOINT_PROBLEM_MARKER_ID);
                        int lineNumber = lineBreakpoint.getLineNumber();
                        String sourceHandle = lineBreakpoint.getSourceHandle();
                        problemMarker.setAttribute(IMarker.LOCATION,    String.valueOf(lineNumber));
                        problemMarker.setAttribute(IMarker.MESSAGE,     description);
                        problemMarker.setAttribute(IMarker.SEVERITY,    severity);
                        problemMarker.setAttribute(IMarker.LINE_NUMBER, lineNumber);
                        if (sourceHandle != null) {
                            problemMarker.setAttribute(ICModelMarker.C_MODEL_MARKER_EXTERNAL_LOCATION, sourceHandle);
                        }

                        // And save the baby
                        fBreakpointMarkerProblems.put(breakpoint, problemMarker);
                    } catch (CoreException e) {
                    }
                }
                return Status.OK_STATUS;
            }
        }.schedule();
    }

    private void removeBreakpointProblemMarker(final ICBreakpoint breakpoint) {

        new Job("Remove Breakpoint Problem Marker") { //$NON-NLS-1$
            @Override
            protected IStatus run(IProgressMonitor monitor) {
                
                IMarker marker = fBreakpointMarkerProblems.remove(breakpoint);
                if (marker != null) {
                    try {
                        marker.delete();
                    } catch (CoreException e) {
                    }
                }

                return Status.OK_STATUS;
            }
        }.schedule();
    }

    //-------------------------------------------------------------------------
    // uninstallBreakpoint
    //-------------------------------------------------------------------------

    /**
     * Un-install an individual breakpoint on the back-end. For one platform
     * breakpoint in a given execution context, there could be multiple
     * corresponding back-end breakpoints (one per thread).
     * 
     * @param dmc
     * @param breakpoint
     * @param rm
     * 
     * @since 4.2
     */
    public void uninstallBreakpoint(final IBreakpointsTargetDMContext dmc, final ICBreakpoint breakpoint, RequestMonitor rm) {
        // Remove all relevant target filters
        // Note that this call is important if a breakpoint is removed directly
        // from the gdb console, or else we will try to re-install it (bug 433044)
       	removeAllTargetFilters(dmc, breakpoint);

        // Remove breakpoint problem marker (if any)
        removeBreakpointProblemMarker(breakpoint);
        
        doUninstallBreakpoint(dmc, breakpoint, new ImmediateRequestMonitor(rm) {
        	@Override
        	protected void handleSuccess() {
        		Map<ICBreakpoint,Map<String,Object>> platformBPs = fPlatformToAttributesMaps.get(dmc);
        		// Note: Protect against NPE. It looks like we just checked the "platformBPs" is not null, 
        		// in doUninstallBreakpoint(), but there is a race condition that can make it null 
        		// in the meantime, if the debug session is destroyed. See bug 511727
        		if (platformBPs != null) {
        			platformBPs.remove(breakpoint);
        		}
        		super.handleSuccess();
        	}
        });
    }

    /*
     * Un-install the target breakpoints associated with the given platform breakpoint. 
     * The information related to the platform breakpoints is not cleared from the session.
     * This allows the target breakpoints to be re-installed when an attribute of the platform 
     * breakpoint is changed.
     */
    private void doUninstallBreakpoint(final IBreakpointsTargetDMContext dmc, final ICBreakpoint breakpoint, final RequestMonitor rm) {
        // Retrieve the breakpoint maps
        final Map<ICBreakpoint,Map<String,Object>> platformBPs = fPlatformToAttributesMaps.get(dmc);
        assert platformBPs != null;

        final Map<ICBreakpoint, Vector<IBreakpointDMContext>> breakpointIDs = fPlatformToBPsMaps.get(dmc);
        assert breakpointIDs != null;

        final Map<IBreakpointDMContext, ICBreakpoint> targetBPs = fBPToPlatformMaps.get(dmc);
        assert targetBPs != null;

        final Map<ICBreakpoint, Set<String>> threadsIDs = fPlatformToBPThreadsMaps.get(dmc);
        assert threadsIDs != null;

        // Minimal validation
        if (!platformBPs.containsKey(breakpoint) || !breakpointIDs.containsKey(breakpoint) || !targetBPs.containsValue(breakpoint)) {
            rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INTERNAL_ERROR, BREAKPOINT_ALREADY_REMOVED, null));
            rm.done();
            return;
        }

        // Remove completion monitor
        // Upon completion, update the mappings
        final CountingRequestMonitor removeRM = new CountingRequestMonitor(getExecutor(), rm) {
            @Override
            protected void handleSuccess() {
                // Update the mappings
                threadsIDs.remove(breakpoint);
                fPendingRequests.remove(breakpoint);

				rm.done();
            }
        };

        // Remove the back-end breakpoints
        // Remove the entry from the breakpointIDs map right away to indicate that we have already
        // taken care of this breakpoint.  This avoids race conditions with the shutdown
        // which would also try to decrement the install count (bug 344635)
        Vector<IBreakpointDMContext> list = breakpointIDs.remove(breakpoint);
        int count = 0;
        if (list != null) {       			
            for (final IBreakpointDMContext bp : list) {
            	targetBPs.remove(bp);
            	decrementInstallCount(bp, breakpoint, new RequestMonitor(getExecutor(), removeRM) {
                	@Override
                	protected void handleCompleted() {
                		fBreakpoints.removeBreakpoint(bp, removeRM); 
                	}
                });
            }
            count = list.size();
            list.clear();  // probably not necessary
        }
        removeRM.setDoneCount(count);
    }

    
    private void decrementInstallCount(IBreakpointDMContext targetDmc, final ICBreakpoint breakpoint, final RequestMonitor rm) {
        fBreakpoints.getBreakpointDMData(targetDmc, new DataRequestMonitor<IBreakpointDMData>(getExecutor(), rm) {
        	@Override
        	protected void handleCompleted() {
        		boolean pending = false;
        		if (isSuccess()) {
        			IBreakpointDMData data = getData();
        			if (data instanceof MIBreakpointDMData) {
        				pending = ((MIBreakpointDMData)data).isPending();
        			}
        		}
        		// Finally, update the platform breakpoint to show it was un-installed.
        		// But we don't do this for pending breakpoints since they were
        		// not marked as installed.
        		if (!pending) {
        			try {
        				breakpoint.decrementInstallCount();
        			} catch (CoreException e) {
        			}
        		}
        		rm.done();
        	}
        });
    }
    //-------------------------------------------------------------------------
    // modifyBreakpoint
    //-------------------------------------------------------------------------

    /**
     * Modify a platform breakpoint which can translate to quite a few updates
     * on the target...
     * 
     * @param dmc
     * @param breakpoint
     * @param attributes
     * @param oldValues
     * @param rm
     */
    private void modifyBreakpoint(final IBreakpointsTargetDMContext dmc, final ICBreakpoint breakpoint,
            final Map<String,Object> attributes, final IMarkerDelta oldValues, final RequestMonitor rm)
    {
        // Retrieve the breakpoint maps
        final Map<ICBreakpoint,Map<String,Object>> platformBPs = fPlatformToAttributesMaps.get(dmc);
        assert platformBPs != null;

        final Map<ICBreakpoint, Vector<IBreakpointDMContext>> breakpointIDs = fPlatformToBPsMaps.get(dmc);
        assert breakpointIDs != null;

        final Map<IBreakpointDMContext, ICBreakpoint> targetBPs = fBPToPlatformMaps.get(dmc);
        assert targetBPs != null;

        final Map<ICBreakpoint, Set<String>> threadsIDs = fPlatformToBPThreadsMaps.get(dmc);
        assert threadsIDs != null;

        boolean filtered = isBreakpointEntirelyFiltered(dmc, breakpoint);
        
        if (filtered && !platformBPs.containsKey(breakpoint)) {
            rm.done();
            return;
        }

        // Check if the breakpoint is installed:
        // the installation might have failed; in this case, we try to install it again because
        // some attribute might have changed which will make the install succeed.
        if (!breakpointIDs.containsKey(breakpoint) && !targetBPs.containsValue(breakpoint)) {
        	if (!filtered) {
        		// Do not try to re-install the breakpoint if the change event is the result of changes 
        		// in the breakpoint's install count.
        		// Excluding ATTR_DEBUGGER_PATH from the comparison because it has been set just before
        		// "modifyBreakpoint()" was called.
        		String[] diff = compareAttributes(oldValues.getAttributes(), attributes, new String[] { ATTR_DEBUGGER_PATH });
        		if (diff.length != 1 || !diff[0].equals(ICBreakpoint.INSTALL_COUNT)) {
	                attributes.put(ATTR_DEBUGGER_PATH, NULL_STRING);
	                attributes.put(ATTR_THREAD_FILTER, extractThreads(dmc, breakpoint));
	                attributes.put(ATTR_THREAD_ID, NULL_STRING);
	                determineDebuggerPath(dmc, attributes, new RequestMonitor(getExecutor(), rm) {
	                    @Override
	                    protected void handleSuccess() {
                			installBreakpoint(dmc, breakpoint, attributes, rm);
	                    }
	                });
        		}
        		else {
        			rm.done();
        		}
        	}
        	else {
                rm.done();
        	}
       		return;
        }
        
        if (filtered) {
        	uninstallBreakpoint(dmc, breakpoint, rm );
        	return;
        }

        // Get the original breakpoint attributes
        final Map<String,Object> originalAttributes = platformBPs.get(breakpoint);
        if (originalAttributes == null) {
            rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INVALID_HANDLE, INVALID_BREAKPOINT, null));
            rm.done();
            return;
        }

        // Determine the attributes delta
        final Map<String,Object> oldAttributes = new HashMap<>(originalAttributes);
        oldAttributes.put(ATTR_THREAD_FILTER, threadsIDs.get(breakpoint));

        final Set<String> newThreads = extractThreads(dmc, breakpoint);
        Map<String,Object> newAttributes = new HashMap<>(attributes);
        newAttributes.put(ATTR_THREAD_FILTER, newThreads);

        final Map<String,Object> attributesDelta = determineAttributesDelta(oldAttributes, newAttributes);

        // Get the list of back-end breakpoints
        final Vector<IBreakpointDMContext> oldTargetBPs = new Vector<>(breakpointIDs.get(breakpoint));
        if (oldTargetBPs.isEmpty()) {
            rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INVALID_HANDLE, INVALID_BREAKPOINT, null));
            rm.done();
            return;
        }

        // We're all set for the breakpoint update.
        //
        // The path for a simple update is straightforward:
        // - For each back-end BP corresponding to a platform BP
        //   - Send an update command to the back-end
        //   - If the operation succeeded, update the data structures
        //   - If the operation failed, try to roll-back
        //
        // In cases where the the back-end breakpoint cannot be
        // simply updated (e.g. thread filter modification), the old
        // breakpoint has to be removed and new one(s) inserted.
        //
        // The path for such an update is:
        // - Install the updated breakpoint
        // - In the operation succeeded
        //   - Remove the old breakpoint(s)
        //   - Perform any pending update

        // Update completion monitor
        final CountingRequestMonitor updateRM = new CountingRequestMonitor(getExecutor(), rm) {
            @Override
            protected void handleSuccess() {
                // Success: simply store the new attributes
                platformBPs.put(breakpoint, attributes);
                rm.done();
            }

            @Override
            protected void handleError() {
            	// Store the error message to use in the problem marker
            	final String errorMessage = getStatus().getException() != null ? 
            		getStatus().getException().getLocalizedMessage() : getStatus().getMessage();
            	// Try to uninstall the target breakpoints and add the problem marker 
            	// with the error message to the platform breakpoint.
				doUninstallBreakpoint(
					dmc, 
					breakpoint, 
					new ImmediateRequestMonitor(rm) {
						@Override
						protected void handleSuccess() {
							addBreakpointProblemMarker(breakpoint, errorMessage, IMarker.SEVERITY_WARNING);
					        rm.done();
						}

						@Override
						protected void handleError() {
			                // Reset the breakpoint attributes. This will trigger a
			                // breakpoint change event and the correct delta will be
			                // computed, resulting in a correctly restored breakpoint
			                // at the back-end.
			                rollbackAttributes(breakpoint, oldValues);
			                platformBPs.put(breakpoint, attributes);

			                rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, REQUEST_FAILED, INVALID_PARAMETER, getStatus().getException()));
			                rm.done();
						};
					}
				);
            }
        };

        // Everything OK: remove the old back-end breakpoints
        final Vector<IBreakpointDMContext> newTargetBPs = new Vector<>();
        final CountingRequestMonitor removeRM = new CountingRequestMonitor(getExecutor(), rm) {
            @Override
            protected void handleSuccess() {
                // All right! Save the new list and perform the final update
                Map<ICBreakpoint, Vector<IBreakpointDMContext>> breakpointIDs = fPlatformToBPsMaps.get(dmc);
                if (breakpointIDs == null) {
                    rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, REQUEST_FAILED, INVALID_BREAKPOINT, null));
                    rm.done();
                    return;
                }
                breakpointIDs.put(breakpoint, newTargetBPs);
                for (IBreakpointDMContext ref : newTargetBPs) {
                    fBreakpoints.updateBreakpoint(ref, attributesDelta, updateRM);
                }
                updateRM.setDoneCount(newTargetBPs.size());
            }};

        // New back-end breakpoints insertion monitor
        // Holds the list of new back-end breakpoint contexts of the platform breakpoint
        final DataRequestMonitor<Vector<IBreakpointDMContext>> insertRM =
            new DataRequestMonitor<Vector<IBreakpointDMContext>>(getExecutor(), null) {

                @Override
                // In theory, we could have had a partial success and the original threads
                // list would be invalid. We think it is highly unlikely so we assume that
                // either everything went fine or else everything failed.
                protected void handleSuccess() {
                    // Get the list of new back-end breakpoints contexts
                    newTargetBPs.addAll(getData());
                    for (IBreakpointDMContext newRef : newTargetBPs) {
                    	targetBPs.put(newRef, breakpoint);
                    }
                    threadsIDs.put(breakpoint, newThreads);
                    for (final IBreakpointDMContext ref : oldTargetBPs) {
                    	targetBPs.remove(ref);
                    	decrementInstallCount(ref, breakpoint, // A tad early but it should work...
	                    	new RequestMonitor(getExecutor(), removeRM) {
	                    		@Override
	                    		protected void handleCompleted() {
	                    			fBreakpoints.removeBreakpoint(ref, removeRM);
	                    		}
	                        }); 
                    }
                    removeRM.setDoneCount(oldTargetBPs.size());
                }
                
                @Override
                protected void handleError() {
                	// Store the error message to use in the problem marker
                	final String errorMessage = getStatus().getException() != null ? 
                		getStatus().getException().getLocalizedMessage() : getStatus().getMessage();
					doUninstallBreakpoint(
						dmc, 
						breakpoint, 
						new ImmediateRequestMonitor(updateRM) {
							@Override
							protected void handleSuccess() {
						        addBreakpointProblemMarker(breakpoint, errorMessage, IMarker.SEVERITY_WARNING);	
						        updateRM.setDoneCount(0);
							};
						}
					);
                }
        	};

        // If the changes in the breakpoint attributes justify it, install a
        // new set of back-end breakpoint(s) and then update them
        if (needsResinstallation(attributesDelta)) {
            reinstallBreakpoint(dmc, breakpoint, attributes, newThreads, insertRM);
        }
        else {
            // Update the back-end breakpoint(s) state
            for (IBreakpointDMContext ref : oldTargetBPs) {
                fBreakpoints.updateBreakpoint(ref, attributesDelta, updateRM);
            }
            updateRM.setDoneCount(oldTargetBPs.size());
        }
    }

    /**
     * Re-install the back-end breakpoints
     * 
     * @param context       the target context
     * @param breakpoint    the platform breakpoint
     * @param attributes    breakpoint augmented attributes
     * @param threads       list of threads where breakpoint is to be installed
     * @param drm           will contain the list of successfully installed back-end breakpoints
     */
    private void reinstallBreakpoint(final IBreakpointsTargetDMContext context, final ICBreakpoint breakpoint,
            final Map<String,Object> attributes, Set<String> threads, final DataRequestMonitor<Vector<IBreakpointDMContext>> drm)
    {
        // Our new list of back-end breakpoints. Built as we go.
        final Vector<IBreakpointDMContext> breakpointList = new Vector<>();

        // Counting monitor for the new back-end breakpoints to install
        // Once we're done, return the new list of back-end breakpoints contexts
        final CountingRequestMonitor installRM = new CountingRequestMonitor(getExecutor(), drm) {
            @Override
            protected void handleSuccess() {
                // Report whatever we have managed to install
                // It is very likely installation either succeeded or failed for all
                drm.setData(breakpointList);
                drm.done();
            }
        };
        installRM.setDoneCount(threads.size());

        // And install the new back-end breakpoints
        for (String thread : threads) {
            // Convert the breakpoint attributes for the back-end
            // Refresh the set of attributes at each iteration just in case...
            Map<String,Object> attrs = convertToTargetBreakpoint(breakpoint, attributes);
            // Tracepoints and dynamic printf are not affected by "skip-all"
            if (!(breakpoint instanceof ICTracepoint) && !(breakpoint instanceof ICDynamicPrintf)
            		&& !fBreakpointManager.isEnabled()) {
                attrs.put(MIBreakpoints.IS_ENABLED, false);
            }

            attrs.put(MIBreakpointDMData.THREAD_ID, thread);

            // Then install the spiked breakpoint
            fBreakpoints.insertBreakpoint(context, attrs,
                new DataRequestMonitor<IBreakpointDMContext>(getExecutor(), installRM) {
                    @Override
                    protected void handleSuccess() {
                        // Add the new back-end breakpoint context to the list
                        breakpointList.add(getData());
                        
                        // Check for a pending breakpoint before showing that it was properly installed
                        fBreakpoints.getBreakpointDMData(getData(), new DataRequestMonitor<IBreakpointDMData>(getExecutor(), null) {
                        	@Override
                        	protected void handleCompleted() {
                        		boolean pending = false;
                        		if (isSuccess()) {
                        			IBreakpointDMData data = getData();
                        			if (data instanceof MIBreakpointDMData) {
                        				pending = ((MIBreakpointDMData)data).isPending();
                        			}
                        		}
                        		// Finally, update the platform breakpoint to show it was installed, unless we have a pending breakpoint
                        		if (!pending) {
                        			try {
                        				breakpoint.incrementInstallCount();
                        			} catch (CoreException e) {
                        			}
                        		}
                        		installRM.done();                        		
                        	}
                        });
                    }

                    @Override
                    protected void handleError() {
                    	String detailedMessage;
                    	if (getStatus().getException() != null &&
                    			getStatus().getException().getMessage() != null) {
                    		detailedMessage = getStatus().getException().getMessage();
                    	} else {
                    		detailedMessage = getStatus().getMessage();
                    	}
                    	String description = (detailedMessage == null) ? 
                    			Messages.Breakpoint_attribute_problem :
                    			MessageFormat.format(Messages.Breakpoint_attribute_detailed_problem, new Object[] { detailedMessage});
                        // Add the new back-end breakpoint context to the list
                        installRM.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, REQUEST_FAILED, description, getStatus().getException()));
                        installRM.done();
                    }
            });
        }
    }

    ///////////////////////////////////////////////////////////////////////////
    // IBreakpointManagerListener implementation
    ///////////////////////////////////////////////////////////////////////////

    /* (non-Javadoc)
     * @see org.eclipse.debug.core.IBreakpointManagerListener#breakpointManagerEnablementChanged(boolean)
     */
	@Override
    public void breakpointManagerEnablementChanged(boolean enabled) {

        // Only modify enabled breakpoints
        for (IBreakpointsTargetDMContext context : fPlatformToBPsMaps.keySet()) {
            for (ICBreakpoint breakpoint : fPlatformToBPsMaps.get(context).keySet()) {
                try {
                    // Note that Tracepoints and dynamic printf are not affected by "skip-all"
                    if (!(breakpoint instanceof ICTracepoint) && !(breakpoint instanceof ICDynamicPrintf)
                    		&& breakpoint.isEnabled()) {
                        for (IBreakpointDMContext ref : fPlatformToBPsMaps.get(context).get(breakpoint)) {
                            Map<String,Object> delta = new HashMap<>();
                            delta.put(MIBreakpoints.IS_ENABLED, enabled);
                            fBreakpoints.updateBreakpoint(ref, delta, new RequestMonitor(getExecutor(), null));
                        }
                    }
                } catch (CoreException e) {
                }
            }
        }
    }

    ///////////////////////////////////////////////////////////////////////////
    // IBreakpointListener implementation
    ///////////////////////////////////////////////////////////////////////////

    @ThreadSafe
	@Override
    public void breakpointAdded(final IBreakpoint breakpoint) {
    	breakpointAdded(breakpoint, null);
    }
    
    /**
     * Extension of {@link #breakpointAdded(IBreakpoint)}
     * @param miBpt the MIBreakpoint that initiated the breakpointAdded, or null
     * @since 5.3
     */
    @ThreadSafe
    public void breakpointAdded(final IBreakpoint breakpoint, MIBreakpoint miBpt) {
        if (supportsBreakpoint(breakpoint)) {
            try {
                // Retrieve the breakpoint attributes
                final Map<String, Object> attrs = breakpoint.getMarker().getAttributes();

                getExecutor().execute(new DsfRunnable() {
                	@Override
                    public void run() {
                        // For a new breakpoint, the first thing we do is set the target filter to all existing processes.
                		// We will need this when it is time to install the breakpoint.
                		// We fetch the processes from our IProcess service to be generic (bug 431986)
                		fProcesses.getProcessesBeingDebugged(fConnection.getContext(), new ImmediateDataRequestMonitor<IDMContext[]>() {
                			@Override
                			protected void handleCompleted() {
                				if (isSuccess()) {
									try {
										IDsfBreakpointExtension filterExtension = getFilterExtension((ICBreakpoint)breakpoint);
										for (IDMContext dmc : getData()) {
											IContainerDMContext containerDmc = DMContexts.getAncestorOfType(dmc, IContainerDMContext.class);
											assert containerDmc != null;
                                    		if (filterExtension.getThreadFilters(containerDmc) == null) {
                                    			// Do this only if there wasn't already an entry, or else we would
                                    			// erase the content of that previous entry.
                                    			// There can be an entry already when a thread-specific breakpoint is created
                                    			// from the MIBreakpointsSynchronizer (through the gdb console).  In that case the
                                    			// platform bp gets created, and the targetFilter gets set by MIBreakpointsSynchronizer
                                    			// before the call to breakpointAdded() is made and we get to here.
                                    			// Bug 433339
                                    			filterExtension.setTargetFilter(containerDmc);
                                    		}
										}
									} catch (CoreException e1) {
										// Error setting target filter, just skip altogether
									}
                				}

                				// Now we can install the bp for all target contexts
                				final CountingRequestMonitor countingRm = new CountingRequestMonitor(getExecutor(), null) {
                					@Override
                					protected void handleCompleted() {
                						// Log any error when creating the breakpoint
                						if (getStatus().getSeverity() == IStatus.ERROR) {
                							GdbPlugin.getDefault().getLog().log(getStatus());
                						}

                					}
                				};
                				countingRm.setDoneCount(fPlatformToAttributesMaps.size());

                                for (final IBreakpointsTargetDMContext dmc : fPlatformToAttributesMaps.keySet()) {
                                    boolean filtered = isBreakpointEntirelyFiltered(dmc, (ICBreakpoint)breakpoint);
                                    if (!filtered) {
                                        determineDebuggerPath(dmc, attrs,
                                                new RequestMonitor(getExecutor(), countingRm) {
                                            @Override
                                            protected void handleSuccess() {
                                                installBreakpoint(dmc, (ICBreakpoint) breakpoint,
                                                        attrs, countingRm);
                                            }
                                        });
                                    } else {
                                        countingRm.done();
                                    }
                                }

                			}
                		});
                	}
                });
            } catch (CoreException e) {
            } catch (RejectedExecutionException e) {
            }
        }
    }

    /**
     * @param bp
     * @return
     * @throws CoreException
     * @since 4.7
     */
    protected IDsfBreakpointExtension getFilterExtension(ICBreakpoint bp) throws CoreException {
        return (IDsfBreakpointExtension) bp.getExtension(GDB_DEBUG_MODEL_ID, ICBreakpointExtension.class);
    }

    /* (non-Javadoc)
     * @see org.eclipse.debug.core.IBreakpointListener#breakpointChanged(org.eclipse.debug.core.model.IBreakpoint, org.eclipse.core.resources.IMarkerDelta)
     */
	@Override
    public void breakpointChanged(final IBreakpoint breakpoint, final IMarkerDelta delta) {

        if (supportsBreakpoint(breakpoint)) {

            try {
                // Retrieve the breakpoint attributes
                final Map<String, Object> attrs = breakpoint.getMarker().getAttributes();
                // Tracepoints and dynamic printf are not affected by "skip-all"
                if (!(breakpoint instanceof ICTracepoint) && !(breakpoint instanceof ICDynamicPrintf) 
                		&& !fBreakpointManager.isEnabled()) {
                    attrs.put(ICBreakpoint.ENABLED, false);
                }

                // Modify the breakpoint in all the target contexts
                getExecutor().execute( new DsfRunnable() {
                	@Override
                    public void run() {

                        // If the breakpoint is currently being updated, queue the request and exit
                        if (fPendingRequests.contains(breakpoint)) {
                            fPendingBreakpoints.add(breakpoint);
                            return;
                        }

                        // Keep track of the updates
                        final CountingRequestMonitor countingRm = new CountingRequestMonitor(getExecutor(), null) {
                            @Override
                            protected void handleCompleted() {

                                if (!isSuccess() &&
                                	getStatus().getSeverity() == IStatus.ERROR) {
                                        GdbPlugin.getDefault().getLog().log(getStatus());
                                }

                                // Indicate that the pending request has completed
                                fPendingRequests.remove(breakpoint);

                                // Process the next pending update for this breakpoint
                                if (fPendingBreakpoints.contains(breakpoint)) {
                                    fPendingBreakpoints.remove(breakpoint);
                                    breakpointChanged(breakpoint, delta);
                                }
                            }
                        };
                        countingRm.setDoneCount(fPlatformToAttributesMaps.size());

                        // Mark the breakpoint as being updated and go
                        fPendingRequests.add(breakpoint);
                        
                        // Modify the breakpoint in all the execution contexts
                        for (final IBreakpointsTargetDMContext dmc : fPlatformToAttributesMaps.keySet()) {
                            determineDebuggerPath(dmc, attrs,
                                new RequestMonitor(getExecutor(), countingRm) {
                                    @Override
                                    protected void handleSuccess() {
                                        modifyBreakpoint(dmc, (ICBreakpoint) breakpoint, attrs, delta, countingRm);
                                    }
                                 });
                        }
                    }
                });
            } catch (CoreException e) {
            } catch (RejectedExecutionException e) {
            }
        }
    }

    /* (non-Javadoc)
     * @see org.eclipse.debug.core.IBreakpointListener#breakpointRemoved(org.eclipse.debug.core.model.IBreakpoint, org.eclipse.core.resources.IMarkerDelta)
     */
	@Override
    public void breakpointRemoved(final IBreakpoint breakpoint, IMarkerDelta delta) {

        if (supportsBreakpoint(breakpoint)) {
            try {
                getExecutor().execute(new DsfRunnable() {
                	@Override
                    public void run() {
                        CountingRequestMonitor countingRm = new CountingRequestMonitor(getExecutor(), null) {
                            @Override
                            protected void handleError() {
                                if (getStatus().getSeverity() == IStatus.ERROR) {
                                    GdbPlugin.getDefault().getLog().log(getStatus());
                                }
                            }
                        };
                        countingRm.setDoneCount(fPlatformToAttributesMaps.size());

                        // Remove the breakpoint in all the execution contexts
                        for (IBreakpointsTargetDMContext dmc : fPlatformToAttributesMaps.keySet()) {
                            if (fPlatformToAttributesMaps.get(dmc).containsKey(breakpoint)) {
                                uninstallBreakpoint(dmc, (ICBreakpoint) breakpoint, countingRm);
                            }
                        }
                    }
                });
            } catch (RejectedExecutionException e) {
            }
        }
    }

    ///////////////////////////////////////////////////////////////////////////
    // IServiceEventListener
    ///////////////////////////////////////////////////////////////////////////

    //-------------------------------------------------------------------------
    // Breakpoints
    //-------------------------------------------------------------------------

    @DsfServiceEventHandler
    public void eventDispatched(BreakpointAddedEvent e) {
    	// Nothing to do - already handled by breakpointAdded()
    }

    @DsfServiceEventHandler
    public void eventDispatched(BreakpointUpdatedEvent e) {
    	// Nothing to do - already handled by breakpointChanged()
    }

    @DsfServiceEventHandler
    public void eventDispatched(BreakpointRemovedEvent e) {
    	// Nothing to do - already handled by breakpointRemoved()
    }

    /*
     * When a watchpoint goes out of scope, it is automatically removed from
     * the back-end. To keep our internal state synchronized, we have to
     * remove it from our breakpoints maps.
     * Unfortunately, GDB doesn't generate the correct event...
     */
    @DsfServiceEventHandler
    public void eventDispatched(MIWatchpointScopeEvent e) {
    }

    //-------------------------------------------------------------------------
    // Breakpoint actions
    //-------------------------------------------------------------------------

	/** @since 4.2 */
	@DsfServiceEventHandler 
    public void eventDispatched(ISuspendedDMEvent e) {
		assert e instanceof IMIDMEvent;
		if (e instanceof IMIDMEvent) {
			Object miEvent = ((IMIDMEvent)e).getMIEvent();

			if (miEvent instanceof MIBreakpointHitEvent) {
				// This covers catchpoints, too
				MIBreakpointHitEvent evt = (MIBreakpointHitEvent)miEvent;
				performBreakpointAction(evt.getDMContext(), evt.getNumber());
				return;
			}

			if (miEvent instanceof MIWatchpointTriggerEvent) {
				MIWatchpointTriggerEvent evt = (MIWatchpointTriggerEvent)miEvent;
				performBreakpointAction(evt.getDMContext(), evt.getNumber());
				return;
			}
		}
	}
	
	/**
	 * @deprecated Replaced by the generic {@link #eventDispatched(ISuspendedDMEvent)}
	 */
	@Deprecated
	@DsfServiceEventHandler
    public void eventDispatched(SuspendedEvent e) {
	}

    private void performBreakpointAction(final IDMContext context, String number) {
        // Identify the platform breakpoint
        final ICBreakpoint breakpoint = findPlatformBreakpoint(number);

        if (breakpoint != null ) {
        	// Perform the actions asynchronously (otherwise we can have a deadlock...)
        	new Job("Breakpoint action") { //$NON-NLS-1$
        		{ setSystem(true); }
        		@Override
        		protected IStatus run(IProgressMonitor monitor) {
        			fBreakpointActionManager.executeActions(breakpoint, new BreakpointActionAdapter(getExecutor(), getServicesTracker(), context));
        			return Status.OK_STATUS;
        		};
        	}.schedule();
        }
    }

    // Helper function to locate the platform breakpoint corresponding
    // to the target breakpoint/watchpoint that was just hit

    // FIXME: (Bug228703) Need a way to identify the correct context where the BP was hit
    private ICBreakpoint findPlatformBreakpoint(String targetBreakpointID) {
        Set<IBreakpointsTargetDMContext> targets = fBPToPlatformMaps.keySet();
        for (IBreakpointsTargetDMContext target : targets) {
            Map<IBreakpointDMContext, ICBreakpoint> bps = fBPToPlatformMaps.get(target);
            Set<IBreakpointDMContext> contexts = bps.keySet();
            for (IBreakpointDMContext context : contexts) {
                if (context instanceof MIBreakpointDMContext) {
                    MIBreakpointDMContext ctx = (MIBreakpointDMContext) context;
                    if (ctx.getReference().equals(targetBreakpointID)) {
                        return bps.get(context);
                    }
                }
            }
        }
        return null;
    }

    /**
     * Returns a platform breakpoint corresponding to a given target breakpoint. 
     * 
     * @since 3.0
     */
    public IBreakpoint findPlatformBreakpoint(IBreakpointDMContext bpContext) {
        if (bpContext instanceof MIBreakpointDMContext) {
        	IBreakpointsTargetDMContext targetCtx = DMContexts.getAncestorOfType(bpContext, IBreakpointsTargetDMContext.class);
        	if (targetCtx != null) {
                Map<IBreakpointDMContext, ICBreakpoint> bps = fBPToPlatformMaps.get(targetCtx);
                if (bps != null) {
                	return bps.get(bpContext);
                }
        	}
        }
        return null;
    }
    
    //-------------------------------------------------------------------------
    // Process/thread start/exit
    //-------------------------------------------------------------------------
    /**
     * @noreference This method is not intended to be referenced by clients.
     * @since 4.4
     */
    @DsfServiceEventHandler
    public void eventDispatched(IStartedDMEvent e) {
    }  
    
    
    private void setTargetFilter(ICBreakpoint breakpoint, IContainerDMContext containerDmc) {
    	try {
    		IDsfBreakpointExtension filterExt = getFilterExtension(breakpoint);
    		if (filterExt.getThreadFilters(containerDmc) == null) {
    			// Do this only if there wasn't already an entry, or else we would
    			// erase the content of that previous entry.
    			// This could theoretically happen if the targetFilter is set by
    			// someone else, before this method is called.
    			// Bug 433339
    			filterExt.setTargetFilter(containerDmc);
    		}

    	} catch (CoreException e) {
    	}
    }
    
    /**
     * @noreference This method is not intended to be referenced by clients.
     * @since 4.4
     */
    @DsfServiceEventHandler
    public void eventDispatched(IExitedDMEvent e) {
		// original code moved to API removeTargetFilter (Bug 456959)
    }

	/**
	 * Remove process from the thread filtering of all breakpoints
	 * @since 4.6
	 */
	public void removeTargetFilter(IContainerDMContext containerDMC) {
		// We must get the list of breakpoints from the platform because our different
		// maps might already have been cleaned up
		IBreakpoint[] allBreakpoints = fBreakpointManager.getBreakpoints(fDebugModelId);
		for (IBreakpoint bp : allBreakpoints) {
			if (supportsBreakpoint(bp)) {
				removeTargetFilter((ICBreakpoint) bp, containerDMC);
			}
		}
	}
    
    private void removeTargetFilter(ICBreakpoint breakpoint, IContainerDMContext containerDmc) {
    	try {
    		IDsfBreakpointExtension filterExt = getFilterExtension(breakpoint);
   			filterExt.removeTargetFilter(containerDmc);
    	} catch (CoreException e) {
    	}
    }

    private void removeAllTargetFilters(IBreakpointsTargetDMContext bpTargetDmc, ICBreakpoint breakpoint) {
    	try {
    		IDsfBreakpointExtension filterExt = getFilterExtension(breakpoint);
   			IContainerDMContext[] targets = filterExt.getTargetFilters();
   			for (IContainerDMContext target : targets) {
   				if (bpTargetDmc.equals(target) || DMContexts.isAncestorOf(target, bpTargetDmc)) {
   					filterExt.removeTargetFilter(target);
   				}
   			}
    	} catch (CoreException e) {
    	}
    }

    //-------------------------------------------------------------------------
    // Session exit
    //-------------------------------------------------------------------------

    /**
     * @since 1.1
     * @nooverride This method is not intended to be re-implemented or extended by clients.
     * @noreference This method is not intended to be referenced by clients.
     */
    @DsfServiceEventHandler
    public void eventDispatched(ICommandControlShutdownDMEvent e) {
        terminated();
    }

    private void terminated() {
    	// Reset the breakpoint install count
    	for (IBreakpointsTargetDMContext ctx : fPlatformToAttributesMaps.keySet()) {
    		Map<ICBreakpoint, Map<String, Object>> breakpoints = fPlatformToAttributesMaps.get(ctx);
            clearBreakpointStatus(breakpoints.keySet().toArray(new ICBreakpoint[breakpoints.size()]), ctx);
    	}
    	// This will prevent Shutdown() from trying to remove bps from a
    	// backend that has already shutdown
        fPlatformToAttributesMaps.clear();
    }

    /**
     * @param bps
     */
    private void clearBreakpointStatus(final ICBreakpoint[] bps, final IBreakpointsTargetDMContext ctx)
    {
        IWorkspaceRunnable wr = new IWorkspaceRunnable() {
        	@Override
            public void run(IProgressMonitor monitor) throws CoreException {
            	// For every platform breakpoint that has at least one target breakpoint installed
            	// we must decrement the install count, for every target breakpoint.
            	// Note that we cannot simply call resetInstallCount() because another
            	// launch may be using the same platform breakpoint.
            	Map<ICBreakpoint, Vector<IBreakpointDMContext>> breakpoints = fPlatformToBPsMaps.get(ctx);
            	for (ICBreakpoint breakpoint : breakpoints.keySet()) {
            		Vector<IBreakpointDMContext> targetBps = breakpoints.get(breakpoint);
            		for (IBreakpointDMContext targetBp : targetBps) {
                        decrementInstallCount(targetBp, breakpoint, new RequestMonitor(getExecutor(), null));
            		}
                }
            }
        };

        // Create the scheduling rule to clear all bp planted.
        ISchedulingRule rule = null;
        List<ISchedulingRule> markerRules = new ArrayList<>();
        for (ICBreakpoint bp : bps) {
            IMarker marker = bp.getMarker();
            if (marker != null) {
                ISchedulingRule markerRule =
                    ResourcesPlugin.getWorkspace().getRuleFactory().markerRule(
                            marker.getResource());
                if (markerRule == null) {
                    markerRules = null;
                    break;
                } else {
                    markerRules.add(markerRule);
                }
            }
        }
        if (markerRules != null) {
            rule = MultiRule.combine(markerRules.toArray(new ISchedulingRule[markerRules.size()]));
        }

        try {
        	// Will run the workspace runnable on the current thread, which
        	// is the DSF executor.
            ResourcesPlugin.getWorkspace().run(wr, rule, 0, null);
        } catch (CoreException e) {
        	GdbPlugin.getDefault().getLog().log(e.getStatus());
        }

        new Job("Clear Breakpoints Status") { //$NON-NLS-1$
            @Override
            protected IStatus run(IProgressMonitor monitor) {
                // Clear any problem markers
                for (IMarker marker : fBreakpointMarkerProblems.values()) {
                	if (marker != null) {
                		try {
							marker.delete();
						} catch (CoreException e) {
						}
                	}
                }
                fBreakpointMarkerProblems.clear();
                
                return Status.OK_STATUS;
            }
        }.schedule();
    }

    ///////////////////////////////////////////////////////////////////////////
    // Support functions
    ///////////////////////////////////////////////////////////////////////////

	/**
	 * Indicates if the platform breakpoint object [bp] is one we can deal with.
	 * For now, it boils down to whether it's a CDT Breakpoint (an
	 * ICBreakpoint). DSF can supports other (custom) types of breakpoints, but
	 * DSF-GDB is tied to ICBreakpoint.
	 * 
	 * @param bp the platform breakpoint
	 * @return true if we support it; false otherwise
	 * @since 4.7
	 */
    protected boolean supportsBreakpoint(IBreakpoint bp) {
        if (bp instanceof ICBreakpoint && bp.getModelIdentifier().equals(fDebugModelId)) {
            IMarker marker = bp.getMarker();
            if (marker != null) {
                return true;
            }
        }
        return false;
    }

    /**
     * determineDebuggerPath
     * 
     * Adds the path to the source file to the set of attributes
     * (for the debugger).
     * 
     * @param dmc
     * @param attributes
     * @param rm
     */
    private void determineDebuggerPath(IBreakpointsTargetDMContext dmc,
            final Map<String, Object> attributes, final RequestMonitor rm)
    {
        String hostPath = (String) attributes.get(ICBreakpoint.SOURCE_HANDLE);

        if (hostPath != null) {

            ISourceLookupDMContext srcDmc = DMContexts.getAncestorOfType(dmc, ISourceLookupDMContext.class);
            if (srcDmc != null) {
                fSourceLookup.getDebuggerPath(srcDmc, hostPath,
                    new DataRequestMonitor<String>(getExecutor(), rm) {
                        @Override
                        protected void handleSuccess() {
                            attributes.put(ATTR_DEBUGGER_PATH, adjustDebuggerPath(getData()));
                            rm.done();
                        }
                    });
            } else {
                // Source lookup not available for given context, use the host
                // path for the debugger path.
                attributes.put(ATTR_DEBUGGER_PATH, adjustDebuggerPath(hostPath));
                rm.done();
            }
        } else {
            // Some types of breakpoints do not require a path
            // (e.g. watchpoints)
            rm.done();
        }
    }

	/**
	 * For some platforms (MinGW) the debugger path needs to be adjusted to work 
	 * with earlier GDB versions. 
	 * See https://bugs.eclipse.org/bugs/show_bug.cgi?id=232415
	 * 
	 * @param path
	 *            the absolute path to the source file
	 * @return the adjusted path provided by the breakpoints service.
	 */
    String adjustDebuggerPath(String path) {
    	return (fBreakpoints instanceof IMIBreakpointPathAdjuster) ? 
    			((IMIBreakpointPathAdjuster)fBreakpoints).adjustDebuggerPath(path) : path;
    }

    /**
     * Determine the set of modified attributes.
     * Elementary set operations in full action :-)
     * 
     * @param oldAttributes
     * @param newAttributes
     * @return
     */
    private Map<String, Object> determineAttributesDelta(Map<String, Object> oldAttributes, Map<String, Object> newAttributes) {

        Map<String, Object> delta = new HashMap<>();

        Set<String> oldKeySet = oldAttributes.keySet();
        Set<String> newKeySet = newAttributes.keySet();

        Set<String> commonKeys  = new HashSet<>(newKeySet); commonKeys.retainAll(oldKeySet);
        Set<String> addedKeys   = new HashSet<>(newKeySet); addedKeys.removeAll(oldKeySet);
        Set<String> removedKeys = new HashSet<>(oldKeySet); removedKeys.removeAll(newKeySet);

        // Add the modified attributes
        for (String key : commonKeys) {
            if (!(oldAttributes.get(key).equals(newAttributes.get(key)))) {
                delta.put(key, newAttributes.get(key));
            }
        }

        // Add the new attributes
        for (String key : addedKeys) {
            delta.put(key, newAttributes.get(key));
        }

        // Remove the deleted attributes
        for (String key : removedKeys) {
            delta.put(key, null);
        }

        return convertToPlatformAttributes(delta);
    }

    /**
     * Converts ICBreakpoint attributes to IBreakpoints attributes.
     * 
     * @param cdtAttributes
     * @return
     * @since 5.3
     */
    protected Map<String, Object> convertToPlatformAttributes(Map<String, Object> cdtAttributes) {

        Map<String,Object> result = new HashMap<String,Object>();

        // IBreakpoint attributes
        if (cdtAttributes.containsKey(ATTR_DEBUGGER_PATH)) {
            result.put(MIBreakpoints.FILE_NAME, cdtAttributes.get(ATTR_DEBUGGER_PATH));
        }
            
        if (cdtAttributes.containsKey(IMarker.LINE_NUMBER)) {
            result.put(MIBreakpoints.LINE_NUMBER, cdtAttributes.get(IMarker.LINE_NUMBER));
        }
            
        if (cdtAttributes.containsKey(BreakpointActionManager.BREAKPOINT_ACTION_ATTRIBUTE)) {
            result.put(MIBreakpoints.COMMANDS, cdtAttributes.get(BreakpointActionManager.BREAKPOINT_ACTION_ATTRIBUTE));
        }

        // ICLineBreakpoint attributes
        if (cdtAttributes.containsKey(ICLineBreakpoint.FUNCTION)) {
            result.put(MIBreakpoints.FUNCTION, cdtAttributes.get(ICLineBreakpoint.FUNCTION));
        }
            
        if (cdtAttributes.containsKey(ICLineBreakpoint.ADDRESS)) {
            result.put(MIBreakpoints.ADDRESS, cdtAttributes.get(ICLineBreakpoint.ADDRESS));
        }
            
        // ICBreakpoint attributes
        if (cdtAttributes.containsKey(ICBreakpoint.CONDITION)) {
            result.put(MIBreakpoints.CONDITION, cdtAttributes.get(ICBreakpoint.CONDITION));
        }
            
        if (cdtAttributes.containsKey(ICBreakpoint.IGNORE_COUNT)) {
            result.put(MIBreakpoints.IGNORE_COUNT, cdtAttributes.get(ICBreakpoint.IGNORE_COUNT));
        }

        if (cdtAttributes.containsKey(ICTracepoint.PASS_COUNT)) {
            result.put(MIBreakpoints.PASS_COUNT, cdtAttributes.get(ICTracepoint.PASS_COUNT));
        }

        if (cdtAttributes.containsKey(ICBreakpoint.ENABLED)) {
            result.put(MIBreakpoints.IS_ENABLED, cdtAttributes.get(ICBreakpoint.ENABLED));
        }

        if (cdtAttributes.containsKey(ICBreakpointType.TYPE)) {
            result.put(MIBreakpoints.BREAKPOINT_TYPE, cdtAttributes.get(ICBreakpointType.TYPE));
        }

        // ICWatchpoint attributes
        if (cdtAttributes.containsKey(ICWatchpoint.EXPRESSION)) {
            result.put(MIBreakpoints.EXPRESSION, cdtAttributes.get(ICWatchpoint.EXPRESSION));
        }

        if (cdtAttributes.containsKey(ICWatchpoint.READ)) {
            result.put(MIBreakpoints.READ, cdtAttributes.get(ICWatchpoint.READ));
        }
        
        if (cdtAttributes.containsKey(ICWatchpoint.WRITE)) {
            result.put(MIBreakpoints.WRITE, cdtAttributes.get(ICWatchpoint.WRITE));
        }

        // Threads
        if (cdtAttributes.containsKey(ATTR_THREAD_FILTER)) {
            result.put(ATTR_THREAD_FILTER, cdtAttributes.get(ATTR_THREAD_FILTER));
        }
        
        // For IDynamicPrintf
        if (cdtAttributes.containsKey(ICDynamicPrintf.PRINTF_STRING)) {
        	result.put(MIBreakpoints.PRINTF_STRING, cdtAttributes.get(ICDynamicPrintf.PRINTF_STRING));
        }

        return result;
    }

    /**
     * Figure out the corresponding number of back-end breakpoints
     * Even though the thread IDs are usually integers, they are
     * stored as strings in CBreakpoints.
     * 
     * @param attributes
     * @return
     */
    @SuppressWarnings("unchecked")
    private Set<String> getThreads(Map<String, Object> attributes) {
        Set<String> threads = (Set<String>) attributes.get(ATTR_THREAD_FILTER);
        if (threads == null) {
            threads = new HashSet<>();
            threads.add("0");    // Thread 0 means all threads //$NON-NLS-1$
        }
        return threads;
    }

    /**
     * Get the list of threads from the breakpoint's thread filtering mechanism
     * 
     * @param breakpoint
     * @return
     */
    private Set<String> extractThreads(IBreakpointsTargetDMContext bpTargetDmc, ICBreakpoint breakpoint) {
        Set<String> results = new HashSet<>();

        if (supportsThreads(breakpoint)) {
        	List<IExecutionDMContext[]> threads = new ArrayList<>(1);

        	try {
        		// Retrieve all existing targets.
        		// Note that these targets can be from different debugging sessions since
        		// they are associated with the platform breakpoint.
        		IDsfBreakpointExtension filterExtension = getFilterExtension(breakpoint);
        		IContainerDMContext[] procTargets = filterExtension.getTargetFilters();

        		// Extract the thread IDs
        		for (IContainerDMContext procDmc : procTargets) {
        			// Look for a target/process that belongs to our session
        			if (procDmc.equals(bpTargetDmc) || DMContexts.isAncestorOf(procDmc, bpTargetDmc)) {
        				IExecutionDMContext[] threadFilters = filterExtension.getThreadFilters(procDmc);
        				if (threadFilters == null) {
        					// The breakpoint applies to the entire process.
        					// For GDB < 7.4, we set the thread to 0 to indicate that the breakpoint
        					// is global for this process.
        					// For GDB >= 7.4, things are more complicated.  There will be one bp for all
        					// processes, so by setting the thread to 0, the breakpoint will apply
        					// to all threads of all processes.  We don't have a choice as there is no 
        					// way to tell GDB to apply to all threads (including any new ones that will
        					// be created) for a single process.
        					// So, in this case, if the bp applies to all threads of one process, it will 
        					// automatically apply to all threads of all processes
        					results.add("0"); //$NON-NLS-1$    
        					return results;
        				} else {
        					threads.add(threadFilters);
        				}
        			}
        		}
        	} catch (CoreException e) {
        		// Error with the thread filtering.  Default to all threads.
        		results.add("0"); //$NON-NLS-1$    
        		return results;
        	}
        	
        	// If there are no threads to filter on, it means the bp applies to the entire process.
        	if (threads.isEmpty()) {
    			results.add("0"); //$NON-NLS-1$    
    			return results;
    		}

        	for (IExecutionDMContext[] targetThreads : threads) {
        		if (targetThreads != null) {
        			for (IExecutionDMContext thread : targetThreads) {
        				if (thread instanceof IMIExecutionDMContext) {
        					results.add(((IMIExecutionDMContext)thread).getThreadId());
        				} else {
        					// If any of the threads is not an IMIExecutionDMContext,
        					// we don't support thread filters at all.
        					results.clear();
        					results.add("0"); //$NON-NLS-1$    
                			return results;
        				}
        			}
        		} else {
        			// Should not happen
        			assert false;
         		}
        	}
        } else {
        	results.add("0"); //$NON-NLS-1$
        }

        return results;
    }

    ///////////////////////////////////////////////////////////////////////////
    // Non-generic (MI-specific) functions
    ///////////////////////////////////////////////////////////////////////////

	/**
	 * Create a collection of DSF-GDB specific breakpoint properties given a
	 * platform/CDT breakpoint object and its properties. Basically, this
	 * determines the set of MI-specific properties to be used in installing the
	 * given breakpoint.
	 * 
	 * @param breakpoint
	 *            the platform breakpoint object; was created by CDT
	 * @param attributes
	 *            the breakpoint's properties. By allowing this to be passed in
	 *            (rather than us calling
	 *            IBreakpoint#getMarker()#getProperties()), we allow the caller
	 *            to specify additional/modified properties.
	 * @return a property bag containing the corresponding DSF-GDB properties
	 */
    protected Map<String,Object> convertToTargetBreakpoint(ICBreakpoint breakpoint, Map<String,Object> attributes) {

        Map<String, Object> properties = new HashMap<>();

        if (breakpoint instanceof ICWatchpoint) {
            properties.put(MIBreakpoints.BREAKPOINT_TYPE, MIBreakpoints.WATCHPOINT);
            properties.put(MIBreakpoints.EXPRESSION,      attributes.get(ICWatchpoint.EXPRESSION));
            properties.put(MIBreakpoints.READ,            attributes.get(ICWatchpoint.READ));
            properties.put(MIBreakpoints.WRITE,           attributes.get(ICWatchpoint.WRITE));
            properties.put(MIBreakpoints.RANGE,           attributes.get(ICWatchpoint2.RANGE));
            properties.put(MIBreakpoints.MEMSPACE,        attributes.get(ICWatchpoint2.MEMORYSPACE));
        }
        else if (breakpoint instanceof ICLineBreakpoint) {
            properties.put(MIBreakpoints.BREAKPOINT_TYPE, MIBreakpoints.BREAKPOINT);
            properties.put(MIBreakpoints.FILE_NAME,       attributes.get(ATTR_DEBUGGER_PATH));
            properties.put(MIBreakpoints.LINE_NUMBER,     attributes.get(IMarker.LINE_NUMBER));
            properties.put(MIBreakpoints.FUNCTION,        attributes.get(ICLineBreakpoint.FUNCTION));
            properties.put(MIBreakpoints.ADDRESS,         attributes.get(ICLineBreakpoint.ADDRESS));
            properties.put(MIBreakpoints.COMMANDS,        attributes.get(BreakpointActionManager.BREAKPOINT_ACTION_ATTRIBUTE));
            
            if (breakpoint instanceof ICTracepoint) {
            	// A tracepoint is a LineBreakpoint, but needs its own type
                properties.put(MIBreakpoints.BREAKPOINT_TYPE, MIBreakpoints.TRACEPOINT);
                properties.put(MIBreakpoints.PASS_COUNT, attributes.get(ICTracepoint.PASS_COUNT));
            } else if (breakpoint instanceof ICDynamicPrintf) {
            	// A DynamicPrintf is a LineBreakpoint, but needs its own type
            	properties.put(MIBreakpoints.BREAKPOINT_TYPE, MIBreakpoints.DYNAMICPRINTF);
            	properties.put(MIBreakpoints.PRINTF_STRING, attributes.get(ICDynamicPrintf.PRINTF_STRING));
            }
        }
        else if (breakpoint instanceof ICEventBreakpoint) {
        	properties.put(MIBreakpoints.BREAKPOINT_TYPE, MIBreakpoints.CATCHPOINT);
        	properties.put(MIBreakpoints.CATCHPOINT_TYPE, GdbCatchpoints.eventToGdbCatchpointKeyword((String)attributes.get(ICEventBreakpoint.EVENT_TYPE_ID)));

        	String arg = (String)attributes.get(ICEventBreakpoint.EVENT_ARG);
        	String[] args;
        	if ((arg != null) && (arg.length() != 0)) {
        		args = new String[1];
        		args[0] = arg;
        	}
        	else {
        		args = new String[0];
        	}
    		properties.put(MIBreakpoints.CATCHPOINT_ARGS, args);
        }
        else {
        	assert false : "platform breakpoint is of an unexpected type: " + breakpoint.getClass().getName(); //$NON-NLS-1$
        }

        // Common fields
        properties.put(MIBreakpoints.CONDITION,           attributes.get(ICBreakpoint.CONDITION));
        properties.put(MIBreakpoints.IGNORE_COUNT,        attributes.get(ICBreakpoint.IGNORE_COUNT));
        properties.put(MIBreakpoints.IS_ENABLED,          attributes.get(ICBreakpoint.ENABLED));
        properties.put(MIBreakpointDMData.THREAD_ID,      attributes.get(ATTR_THREAD_ID));

        // checks for the breakpoint type, and adds the hardware/temporary flags 
        Object breakpointType = attributes.get(ICBreakpointType.TYPE);
       	if (breakpointType instanceof Integer) {
            boolean isHardware = ((Integer) breakpointType & ICBreakpointType.HARDWARE) == ICBreakpointType.HARDWARE;
            boolean isTemporary = ((Integer) breakpointType & ICBreakpointType.TEMPORARY) == ICBreakpointType.TEMPORARY;
            properties.put(MIBreakpointDMData.IS_HARDWARE, isHardware);
            properties.put(MIBreakpointDMData.IS_TEMPORARY, isTemporary);
        }

        // Adjust for "skip-all"
        // Tracepoints and dynamic printf are not affected by "skip-all"
        if (!(breakpoint instanceof ICTracepoint) && !(breakpoint instanceof ICDynamicPrintf) 
        		&& !fBreakpointManager.isEnabled()) {
            properties.put(MIBreakpoints.IS_ENABLED, false);
        }

        return properties;
    }

    /**
     * Determine if the modified attributes necessitate
     * a breakpoint removal/re-installation
     * 
     * @param delta
     * @return
     */
    protected boolean needsResinstallation(Map<String,Object> delta) {
        
        // Check if there is any modified attribute
        if (delta == null) {
            return false;
        }

        // Check the "critical" attributes
        if (delta.containsKey(ATTR_DEBUGGER_PATH)            // File name
        ||  delta.containsKey(MIBreakpoints.LINE_NUMBER)     // Line number
        ||  delta.containsKey(MIBreakpoints.BREAKPOINT_TYPE)     // breakpoint type
        ||  delta.containsKey(MIBreakpoints.FUNCTION)        // Function name
        ||  delta.containsKey(MIBreakpoints.ADDRESS)         // Absolute address
        ||  delta.containsKey(ATTR_THREAD_FILTER)            // Thread ID
        ||  delta.containsKey(MIBreakpoints.EXPRESSION)      // Watchpoint expression
        ||  delta.containsKey(MIBreakpoints.READ)            // Watchpoint type
        ||  delta.containsKey(MIBreakpoints.WRITE)           // Watchpoint type
        ||  delta.containsKey(MIBreakpoints.PRINTF_STRING)) {// Dprintf string
            return true;
        }

         return false;
    }

    /**
     * @param breakpoint
     * @param oldValues
     */
    protected void rollbackAttributes(ICBreakpoint breakpoint, IMarkerDelta oldValues) {

        try {
            String newCondition = breakpoint.getCondition();
            if (newCondition == null) {
                newCondition = NULL_STRING;
            }
            String oldCondition = (oldValues != null) ? oldValues.getAttribute(ICBreakpoint.CONDITION, NULL_STRING) : NULL_STRING;
            if (!oldCondition.equals(newCondition)) {
                breakpoint.setCondition(oldCondition);
            }
            else {
                breakpoint.setCondition(NULL_STRING);
            }
        } catch (CoreException e) {
        }
    }

    /**
     * Indicates if the back-end supports multiple threads for
     * this type of breakpoint
     * 
     * @param breakpoint
     */
    protected boolean supportsThreads(ICBreakpoint breakpoint) {

        return !(breakpoint instanceof ICWatchpoint);
    }

    /**
     * Returns whether the breakpoint is filtered for the given target.
     */
    private boolean isBreakpointEntirelyFiltered(IBreakpointsTargetDMContext bpTargetDmc, ICBreakpoint breakpoint) {    	
    	try {
    		IContainerDMContext[] procTargets = getFilterExtension(breakpoint).getTargetFilters();
    		for (IContainerDMContext procDmc : procTargets) {
    			if (procDmc.equals(bpTargetDmc) || DMContexts.isAncestorOf(procDmc, bpTargetDmc)) {
    				return false;
    			}
    		}
    	} catch (CoreException e) {
    	}
    	return true;
    }

    /**
	 * @since 4.2
	 */
    public void addBreakpointsTrackingListener(IMIBreakpointsTrackingListener listener) {
    	fTrackingListeners.add(listener);
    }

    /**
	 * @since 4.2
	 */
    public void removeBreakpointsTrackingListener(IMIBreakpointsTrackingListener listener) {
    	fTrackingListeners.remove(listener);
    }

	private String[] compareAttributes(Map<String, Object> oldAttr, Map<String, Object> newAttr, String[] exclude) {
		List<String> list = new ArrayList<String>();
		Set<String> names = new HashSet<String>(oldAttr.keySet());
		names.addAll(newAttr.keySet());
		for (String n : names) {
			if (exclude != null && Arrays.asList(exclude).contains(n)) {
				continue;
			}
			Object oldValue = oldAttr.get(n);
			if (oldValue != null && !oldValue.equals(newAttr.get(n))) {
				list.add(n);
			}
			else if (oldValue == null) {
				if (newAttr.get(n) != null) {
					list.add(n);
				}
			}
		}
		return list.toArray(new String[list.size()]);
	}
}
